/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.commands.factories;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.gef.commands.Command;
import org.eclipse.uml2.uml.Element;


/**
 * 
 * 
 * @author Remi SCHNEKENBURGER
 */
public class GenericCommandFactory {
	//	 make private to force singleton use
	/**
	 * 
	 */
	private GenericCommandFactory() {
	}

	/**
	 * 
	 */
	public static GenericCommandFactory eINSTANCE = new GenericCommandFactory();

	/**
	 * 
	 * 
	 * @param attributeName 
	 * @param element 
	 * @param parameters 
	 * 
	 * @return 
	 */
	public Command createCommand(Element element, String attributeName, Object[] parameters) {
		EAttribute attribute = lookForModifiedEAttribute(element, attributeName);
		boolean isMultiValuedAttribute = (attribute.getUpperBound() != 1 );
		Command command = null;
		if(!isMultiValuedAttribute) {
			command = new SetSimpleValueCommand(element, attributeName, parameters);
		} else {

		}

		return command;
	}

	/**
	 * Look for a given EAttribute, given its name.
	 * 
	 * @param element 
	 * @param name the name of the EAttribute to find
	 * 
	 * @return the found attribute or null
	 */
	private EAttribute lookForModifiedEAttribute(Element element, String name) {
		Assert.isNotNull(element);
		Iterator<EAttribute> it = element.eClass().getEAllAttributes().iterator();
		while (it.hasNext()) {
			EAttribute attribute = it.next();
			if(name.equalsIgnoreCase(attribute.getName())) {
				return attribute;
			}
		}
		return null;
	}

	/**
	 * 
	 * 
	 * @param parameters 
	 * 
	 * @return 
	 */
	private Class[] getParametersTypes(Object[] parameters) {
		int length = parameters.length;
		Class[] types = new Class[length];
		for(int i = 0; i < length ; i++) {
			if(parameters[i].getClass().equals(java.lang.Boolean.class)) {
				types[i] = boolean.class;
				
			} else if(parameters[i].getClass().equals(Integer.class)) {
				types[i] = int.class;
			} else {
				types[i]=parameters[i].getClass();
			}
		}
		return types;
	}


	///////////////////////////////////////////////////////////////////////////
	// Simple Value Attribute modification
	///////////////////////////////////////////////////////////////////////////

	/**
	 * Generic command for UML element modifications.
	 * 
	 * @author Remi SCHNEKENBURGER
	 */
	private class SetSimpleValueCommand extends Command {
		
		/**
		 * 
		 */
		private Element element;
		
		/**
		 * 
		 */
		private String attributeName;
		
		/**
		 * 
		 */
		private Object[] parameters;
		
		/**
		 * 
		 */
		private Object saveObject;
		
		/**
		 * 
		 */
		private Method setterMethod;
		
		/**
		 * 
		 */
		private Method getterMethod;

		/**
		 * 
		 * 
		 * @param attributeName 
		 * @param element 
		 * @param parameters 
		 */
		public SetSimpleValueCommand(Element element, String attributeName, Object[] parameters) {
			super();
			this.element = element;
			this.attributeName = attributeName;
			this.parameters = parameters;
			String element_name = element.eClass().getName();
			setLabel("modify <"+element_name+"> "+attributeName);
		}

		/* (non-Javadoc)
		 * @see org.eclipse.gef.commands.Command#execute()
		 */
		/**
		 * 
		 */
		@Override
		public void execute() {
			// 2 solutions => multi valued attribute or single value
			Class[] parametersTypes = getParametersTypes(parameters);
			// first: single value (ex: t1.setValue(parem2))
			try {
				setterMethod = getSetterMethod(element.getClass(), attributeName, parametersTypes);
				getterMethod = getGetterMethod(element.getClass(), attributeName, new Class[] {});

				saveObject = getterMethod.invoke(element, new Object[] {});
				setterMethod.invoke(element, parameters);
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}

		}

		/**
		 * 
		 * 
		 * @param attributeName 
		 * @param class_ 
		 * @param parametersTypes 
		 * 
		 * @return 
		 */
		private Method getSetterMethod(Class class_, String attributeName, Class[] parametersTypes) {
			Method setMethod = null;
			try {
				setMethod = class_.getMethod("set"+attributeName, parametersTypes);
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			}
			return setMethod;
		}
		
		/**
		 * 
		 * 
		 * @param attributeName 
		 * @param class_ 
		 * @param parametersTypes 
		 * 
		 * @return 
		 */
		private Method getGetterMethod(Class class_, String attributeName, Class[] parametersTypes) {
			Method getMethod = null;
			try {
				// if name = IsXXX, than it is simply isXXX(), else it is getXXX
				if(attributeName.toLowerCase().startsWith("is")) {
					// warning : if start with "Is", the first letter must be lowered
					getMethod = class_.getMethod(attributeName.replaceFirst("I", "i"), parametersTypes);
				}
				else {
					getMethod = class_.getMethod("get"+attributeName, parametersTypes);
				}
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			}
			return getMethod;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.gef.commands.Command#redo()
		 */
		/**
		 * 
		 */
		@Override
		public void redo() {
			execute();
		}

		/* (non-Javadoc)
		 * @see org.eclipse.gef.commands.Command#undo()
		 */
		/**
		 * 
		 */
		@Override
		public void undo() {
			try {
				setterMethod.invoke(element, saveObject);
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		/**
		 * 
		 * 
		 * @return 
		 */
		@Override
		public String toString() {
			return this.getLabel();
		}
	}
}
